package gitee.derumin.teleport_compass.waypoints;

import gitee.derumin.lib_for_derumin.api.features.ChunkDataTools;
import gitee.derumin.lib_for_derumin.logger.DeruminLogger;
import gitee.derumin.lib_for_derumin.tools.ArgumentTransmitter;
import gitee.derumin.teleport_compass.Teleport_compass;
import net.minecraft.block.Blocks;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.LodestoneTrackerComponent;
import net.minecraft.component.type.LoreComponent;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.decoration.DisplayEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtHelper;
import net.minecraft.nbt.NbtList;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.GlobalPos;
import net.minecraft.world.World;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.WorldChunk;

import java.util.*;

public class Waypoints implements ChunkDataTools.TickableChunkData {
    public static final Identifier ID = Identifier.of(Teleport_compass.NAMESPACE, "waypoints");
    private final Map<BlockPos, Waypoint> waypoints;
    private boolean updated;

    public static void init(){
        ChunkDataTools.registerChunkData(new Waypoints());
    }

    private Waypoints(){
        this.waypoints = new HashMap<>();
        this.updated = false;
    }

    @Override
    public Identifier getId() {
        return ID;
    }

    @Override
    public ChunkDataTools.TickableChunkData newChunkData(Chunk chunk) {
        return new Waypoints();
    }

    @Override
    public void readFromNbt(NbtCompound nbtCompound) {
        NbtList list = nbtCompound.getList("waypoints", 10);
        for (NbtElement nbtElement : list) {
            NbtCompound compound = (NbtCompound) nbtElement;
            Waypoint waypoint = Waypoint.fromNbt(compound);
            if (waypoint != null) {
                this.waypoints.put(waypoint.pos, waypoint);
            }
        }
    }

    @Override
    public void writeToNbt(NbtCompound nbtCompound) {
        NbtList list = new NbtList();
        this.waypoints.values().forEach(waypoint -> list.add(waypoint.writeNbt()));
        nbtCompound.put("waypoints", list);
    }

    @Override
    public void tick(WorldChunk worldChunk, int i) {
        Iterator<Map.Entry<BlockPos, Waypoint>> iterator = this.waypoints.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<BlockPos, Waypoint> entry = iterator.next();
            if (!entry.getValue().tick(worldChunk)){
                iterator.remove();
                this.updated = true;
                DeruminLogger.getLogger().warn("位于" + entry.getKey().toShortString() + "的路标'" + entry.getValue().name.getString() + "'被摧毁了。", DeruminLogger.AnnounceType.ALL);
            }
        }
    }

    @Override
    public void skipTime(Chunk chunk, long l) {}

    @Override
    public boolean needsSaving() {
        return this.updated;
    }

    @Override
    public void setNeedsSaving(boolean b) {
        this.updated = b;
    }

    public void trySetWaypoint(ServerWorld world, BlockPos pos, Text name){
        if (!world.isInBuildLimit(pos) || !world.getBlockState(pos).isOf(Blocks.LODESTONE) || this.waypoints.containsKey(pos)){
            return;
        }
        Waypoint waypoint = new Waypoint(pos, name);
        this.waypoints.put(pos, waypoint);
        this.updated = true;
    }

    public boolean hasWaypoint(BlockPos pos){
        return this.waypoints.containsKey(pos);
    }

    public String getWaypointName(BlockPos pos){
        return this.hasWaypoint(pos)? this.waypoints.get(pos).name.getString() : null;
    }

    public boolean link(ItemStack stack, BlockPos pos, PlayerEntity player, World world){
        ArgumentTransmitter<Boolean> b = new ArgumentTransmitter<>(false);
        this.waypoints.forEach((pos1, waypoint) -> {
            if (pos1.equals(pos)){
                b.setValue(true);
                world.playSound(null, pos, SoundEvents.ITEM_LODESTONE_COMPASS_LOCK, SoundCategory.PLAYERS, 1.0F, 1.0F);
                boolean bl = !player.isInCreativeMode() && stack.getCount() == 1;
                LodestoneTrackerComponent lodestoneTrackerComponent = new LodestoneTrackerComponent(Optional.of(GlobalPos.create(world.getRegistryKey(), pos)), true);
                List<Text> texts = new ArrayList<>();
                if (Teleport_compass.CONFIG.internationalization){
                    texts.add(Text.translatable("item.teleport_compass.teleport_compass.lore1"));
                    texts.add(Text.translatable("item.teleport_compass.teleport_compass.lore2"));
                    texts.add(Text.translatable("item.teleport_compass.teleport_compass.lore3"));
                }else {
                    texts.add(Text.of("右键传送到路标处。"));
                    texts.add(Text.of("这可能会花费一些经验。"));
                    texts.add(Text.of("如果路标损坏，就会消失。"));
                }
                if (bl) {
                    stack.set(DataComponentTypes.LODESTONE_TRACKER, lodestoneTrackerComponent);
                    stack.set(DataComponentTypes.ITEM_NAME, Teleport_compass.COMPASS_NAME_LINKED);
                    stack.set(DataComponentTypes.CUSTOM_NAME, waypoint.name.copy());
                    stack.set(DataComponentTypes.LORE, new LoreComponent(texts));
                } else {
                    ItemStack stack1 = stack.copyWithCount(1);
                    stack.decrementUnlessCreative(1, player);
                    stack1.set(DataComponentTypes.LODESTONE_TRACKER, lodestoneTrackerComponent);
                    stack1.set(DataComponentTypes.ITEM_NAME, Teleport_compass.COMPASS_NAME_LINKED);
                    stack1.set(DataComponentTypes.CUSTOM_NAME, waypoint.name.copy());
                    stack1.set(DataComponentTypes.LORE, new LoreComponent(texts));
                    if (!player.getInventory().insertStack(stack1)) {
                        player.dropItem(stack1, false);
                    }
                }
            }
        });
        return b.getValue();
    }

    private static class Waypoint {
        private final BlockPos pos;
        private final Text name;
        private final int yawOffset;
        private DisplayEntity.TextDisplayEntity showEntity;

        private Waypoint(BlockPos pos, Text name){
            this.pos = pos;
            this.name = name;
            this.yawOffset = (int) (Math.random() *360);
            this.showEntity = null;
        }

        private boolean tick(WorldChunk chunk){
            if (chunk.getBlockState(this.pos).isOf(Blocks.LODESTONE)){
                if ((this.showEntity == null || this.showEntity.isRemoved()) && chunk.getWorld() instanceof ServerWorld serverWorld){
                    this.showEntity = makeEntity(serverWorld);
                }
                if (this.showEntity != null && !this.showEntity.isRemoved()){
                    int i = (int) (chunk.getWorld().getTime() %180);
                    this.showEntity.setPos(this.pos.getX() +0.5d, this.pos.getY() +1.5d + (i > 90? i : 180-i)/90d, this.pos.getZ() +0.5d);
                    this.showEntity.setYaw((i*2 +yawOffset)%360);
                }
                return true;
            }else {
                this.showEntity.kill();
                return false;
            }
        }

        private DisplayEntity.TextDisplayEntity makeEntity(ServerWorld world){
            DisplayEntity.TextDisplayEntity entity;
            do {
                entity = new DisplayEntity.TextDisplayEntity(EntityType.TEXT_DISPLAY, world);
            }while (!world.spawnNewEntityAndPassengers(entity));
            entity.setText(this.name);
            entity.teleport_compass$setLinked();
            return entity;
        }

        private static Waypoint fromNbt(NbtCompound nbt){
            Optional<BlockPos> opos = NbtHelper.toBlockPos(nbt, "pos");
            if (opos.isEmpty() || !nbt.contains("name", 8)){
                return null;
            }
            Text text = Text.of(nbt.getString("name"));
            return new Waypoint(opos.get(), text);
        }

        private NbtCompound writeNbt(){
            NbtCompound nbt = new NbtCompound();
            nbt.putString("name", this.name.getString());
            nbt.put("pos", NbtHelper.fromBlockPos(this.pos));
            return nbt;
        }
    }
}
